#include "call_js_function.hpp"
#include <condition_variable>
#include <future>

napi_value demo_call_js_function::PassingHelloCallHello(napi_env env, napi_callback_info info)
{
    napi_value result = nullptr;
    napi_value argv[1];
    size_t argc = 1;
    napi_value jsthis;
    
    napi_status status;
    
    status = napi_get_cb_info(env, info, &argc, argv, &jsthis, nullptr);
    STATUS_CHECK(status);
    
    // 回调函数原型为 void func(number), 需要传入一个入参int
    napi_value parm;
    uint32_t sum = 10;
    status = napi_create_int32(env, sum, &parm);
    STATUS_CHECK(status);
    status = napi_call_function(env, jsthis, argv[0], 1, &parm, nullptr);
    STATUS_CHECK(status);
    
    bool pending = false;
    status = napi_is_exception_pending(env, &pending);
    STATUS_CHECK(status);
    if (pending) {
        LOGINFO("exist exception! %{public}s %{public}d", __FILE__, __LINE__);
    }
    
    return result;
}

static napi_value CallbackFn(napi_env env, napi_callback_info info)
{
    napi_value result = nullptr;
    size_t argc = 2;
    napi_value argv[2];
    
    napi_status status;
    status = napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
    STATUS_CHECK(status);
    
    char value[BUFF_SIZE] = {0};
    char key[BUFF_SIZE] = {0};
    size_t len = 0;
    status = napi_get_value_string_utf8(env, argv[0], value, BUFF_SIZE, &len);
    STATUS_CHECK(status);
    
    status = napi_get_value_string_utf8(env, argv[1], key, BUFF_SIZE, &len);
    STATUS_CHECK(status);

    LOGINFO("value:%{public}s  key:%{public}s", value, key);
    
    return result;
}

napi_value demo_call_js_function::PassingMapCallMapFunc(napi_env env, napi_callback_info info)
{
    napi_value result = nullptr;
    napi_value argv[1] = {nullptr};
    size_t argc = 1;
    napi_status status;
    
    status = napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
    STATUS_CHECK(status);
    
    napi_value forEachfunc;
    status = napi_get_named_property(env, argv[0], "forEach", &forEachfunc);
    STATUS_CHECK(status);
    
    // js 的forEach调用为  forEach((value, key, Map<key, value>) => {}), 需要创建一个JS函数
    napi_value callbackfn;
    status = napi_create_function(env, "forEachCallback", NAPI_AUTO_LENGTH, CallbackFn, nullptr, &callbackfn);
    STATUS_CHECK(status);
    
    status = napi_call_function(env, argv[0], forEachfunc, 1, &callbackfn, nullptr);
    STATUS_CHECK(status);
    
    bool pending = false;
    status = napi_is_exception_pending(env, &pending);
    STATUS_CHECK(status);
    if (pending) {
        LOGINFO("exist exception! %{public}s %{public}d", __FILE__, __LINE__);
    }

    return result;
}


napi_value demo_call_js_function::CplusThreadCallJsFunc(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value argv[1] = {nullptr};
    napi_value result = nullptr;
    napi_status status;
    status = napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
    STATUS_CHECK(status);

    napi_ref *ref = new napi_ref;
    status = napi_create_reference(env, argv[0], 1, ref);
    
    napi_value workName;
    napi_threadsafe_function *threadsafeFunc = new napi_threadsafe_function;
    status = napi_create_string_utf8(env, "napi_create_threadsafe_function", NAPI_AUTO_LENGTH, &workName);
    status = napi_create_threadsafe_function(
        env, argv[0], nullptr, workName, 0, 1, ref,
        [](napi_env env, void *data, void *hint) {
            napi_ref *ref = reinterpret_cast<napi_ref *>(data);
            uint32_t len;
            napi_reference_unref(env, *ref, &len);
            napi_delete_reference(env, *ref);
            delete ref;
            LOGINFO("delete threadsafe data");
        },
        ref, [](napi_env env, napi_value cb, void *context, void *data){
            napi_ref *ref = reinterpret_cast<napi_ref*>(context);
            napi_value func;
            napi_get_reference_value(env, *ref, &func);
            napi_value result = nullptr;
            napi_call_function(env, nullptr, func, 0, nullptr, &result);
        }, threadsafeFunc);

    std::thread t([threadsafeFunc]() {
        napi_call_threadsafe_function(*threadsafeFunc, nullptr, napi_tsfn_blocking);
        napi_release_threadsafe_function(*threadsafeFunc, napi_tsfn_release);
        delete threadsafeFunc;
    });
    t.detach();
    
    return result;
}


static void call_js(napi_env env, napi_value js_callback, void *context, void *data)
{
    napi_handle_scope scope;
    napi_open_handle_scope(env, &scope);
    
    void *func = data;
    
    napi_ref * ref = reinterpret_cast<napi_ref *>(context);
    
    napi_status status;
    napi_value argv[2];
    
    status = napi_create_int32(env, 2000, &argv[0]);
    
    status = napi_create_function(
        env, "call_js", NAPI_AUTO_LENGTH,
        [](napi_env env, napi_callback_info info) -> napi_value {
            size_t argc = 1;
            napi_value argv[1] = {nullptr};
            napi_value result = nullptr;
            napi_status status;
            std::function<void(std::string)> *func;
            status = napi_get_cb_info(env, info, &argc, argv, nullptr, (void**)&func);
        
            char buf[BUFF_SIZE];
            size_t len = 0;
            status = napi_get_value_string_utf8(env, argv[0], buf, BUFF_SIZE, &len);
            (*func)(buf);
            return result;
        },
        func, &argv[1]);

    napi_value jsfunc;
    status = napi_get_reference_value(env, *ref, &jsfunc);
    
    napi_value res;
    status = napi_call_function(env, nullptr, jsfunc, 2, argv, &res);
    
    napi_close_handle_scope(env, scope);
}


// 该函数将JS异步函数在C++子线程中转成同步函数调用，利用std::promise
napi_value demo_call_js_function::TransferAsyncToSync(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value argv[1] = {nullptr};
    napi_value result = nullptr;
    napi_status status;
    status = napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
    STATUS_CHECK(status);
    
    // 创建生命周期，防止该函数执行完后，变量被gc回收
    napi_ref *ref = new napi_ref;
    status = napi_create_reference(env, argv[0], 1, ref);
    
    // 创建线程安全函数， JS函数后面会在c++线程中执行
    napi_value workName;
    napi_threadsafe_function *threadsafeFunc = new napi_threadsafe_function;
    status = napi_create_string_utf8(env, "napi_create_threadsafe_function", NAPI_AUTO_LENGTH, &workName);
    status = napi_create_threadsafe_function(env, argv[0], nullptr, workName, 0, 1, ref, [](napi_env env,void *data,void *hint){
        napi_ref *ref = reinterpret_cast<napi_ref*>(data);
        uint32_t len;
        napi_reference_unref(env, *ref, &len);
        napi_delete_reference(env, *ref);
        delete ref;
        LOGINFO("delete threadsafe data");
    }, ref, call_js, threadsafeFunc);
    
    // 启用任务线程， 所有任务在该线程中完成
    std::thread t([threadsafeFunc](){
        
        std::promise<std::string> pro;
        
        std::function<void(std::string)> func = [&pro](std::string msg) {
            pro.set_value(msg);
        };
        LOGINFO("start call async func");
        
        napi_call_threadsafe_function(*threadsafeFunc, &func, napi_tsfn_blocking);
        
        std::string str = pro.get_future().get();
        
        LOGINFO("async func result : %{public}s", str.c_str());

        napi_release_threadsafe_function(*threadsafeFunc, napi_tsfn_release);
        delete threadsafeFunc;
    });
    
    t.detach();
    return result;
}

napi_value demo_call_js_function::Exports(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        {"PassingHelloCallHello", nullptr, demo_call_js_function::PassingHelloCallHello,nullptr, nullptr, nullptr, napi_default, nullptr},
        {"PassingMapCallMapFunc", nullptr, demo_call_js_function::PassingMapCallMapFunc,nullptr, nullptr, nullptr, napi_default, nullptr},
        {"CplusThreadCallJsFunc", nullptr, demo_call_js_function::CplusThreadCallJsFunc,nullptr, nullptr, nullptr, napi_default, nullptr},
        {"TransferAsyncToSync", nullptr, demo_call_js_function::TransferAsyncToSync,nullptr, nullptr, nullptr, napi_default, nullptr}
    };
    
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}